package com.kdgc.energy.dmm.insert;

import com.kdgc.energy.dmm.Environment;
import com.kdgc.energy.dmm.lambda.SerializedFunction;
import com.kdgc.energy.dmm.table.Table;
import com.kdgc.energy.dmm.time.TimeStatistical;
import org.apache.metamodel.UpdateCallback;
import org.apache.metamodel.UpdateSummary;
import org.apache.metamodel.data.Style;
import org.apache.metamodel.insert.InsertInto;
import org.apache.metamodel.insert.RowInsertionBuilder;
import org.apache.metamodel.jdbc.JdbcDataContext;
import org.apache.metamodel.schema.Column;
import org.apache.metamodel.schema.MutableColumn;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author xu.wenchang
 * @version 1.0 2022/04/19
 */
public class Insert {
    private Class<?> clazz;
    private Map<MutableColumn, Object> map;
    private TimeStatistical timeStatistical;
    
    public Insert(Class<?> clazz) {
        this.clazz = clazz;
        this.map = new LinkedHashMap<>();
        this.timeStatistical = new TimeStatistical();
        this.timeStatistical.phaseStart("build");
    }
    
    public <T, R> Insert value(SerializedFunction<T, R> sf, Object value) {
        this.map.put(Environment.get(sf), value);
        return this;
    }
    
    public Insert value(Object obj) {
        assert null != obj : "obj不能为null.";
        
        if (clazz.isAssignableFrom(obj.getClass())) {
            Field[] fields = obj.getClass()
                                .getDeclaredFields();
            Table table = Environment.get(clazz);
            
            for (Field f : fields) {
                if (Modifier.isStatic(f.getModifiers())) {
                    continue;
                }
                
                try {
                    f.setAccessible(true);
                    Object value = f.get(obj);
                    
                    if (null == value) {
                        continue;
                    }
                    
                    MutableColumn column = table.getColumnByField(f.getName());
                    
                    if (null != column) {
                        map.put(column, value);
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        
        return this;
    }
    
    public boolean execute() {
        final JdbcDataContext dataContext = new JdbcDataContext(Environment.getDataSource());
        String tableName = Environment.getTableName(this.clazz);
        final org.apache.metamodel.schema.Table table = dataContext.getTableByQualifiedLabel(tableName);
        UpdateSummary updateSummary = dataContext.executeUpdate(updateCallback -> {
            InsertInto insertInto = new InsertInto(table);
            
            for (Map.Entry<MutableColumn, Object> entry : Insert.this.map.entrySet()) {
                insertInto.value(entry.getKey()
                                      .getName(), entry.getValue());
            }
            
            try {
                
                String sql = Insert.this.toSQL(insertInto, dataContext, updateCallback);
                Insert.this.timeStatistical.setName(sql);
                if (Environment.isShowSQL()) {
                    Environment.getWriter()
                               .write(sql);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            
            Insert.this.timeStatistical.phaseEnd("build");
            Insert.this.timeStatistical.phaseStart("execute");
            insertInto.run(updateCallback);
            Insert.this.timeStatistical.phaseEnd("execute");
        });
        
        this.timeStatistical.end();
        
        if (Environment.isStatExecTime()) {
            Environment.getWriter()
                       .write(this.timeStatistical.statisticalInfo());
        }
        
        return updateSummary.getInsertedRows()
                            .orElse(0) > 0;
    }
    
    private String toSQL(InsertInto insertInto, JdbcDataContext dataContext, UpdateCallback callback) throws Exception {
        String tableName = Environment.getTableName(this.clazz);
        final org.apache.metamodel.schema.Table table = dataContext.getTableByQualifiedLabel(tableName);
        RowInsertionBuilder insertBuilder = callback.insertInto(table);
        
        Class<?> superClass = org.apache.metamodel.update.Update.class.getSuperclass();
        
        Field columnsField = superClass.getDeclaredField("_columns");
        columnsField.setAccessible(true);
        Column[] columns = (Column[]) columnsField.get(insertInto);
        
        Field valueField = superClass.getDeclaredField("_values");
        valueField.setAccessible(true);
        Object[] values = (Object[]) valueField.get(insertInto);
        
        Field stylesField = superClass.getDeclaredField("_styles");
        stylesField.setAccessible(true);
        Style[] styles = (Style[]) stylesField.get(insertInto);
        
        Field explicitNullsField = superClass.getDeclaredField("_explicitNulls");
        explicitNullsField.setAccessible(true);
        boolean[] explicitNulls = (boolean[]) explicitNullsField.get(insertInto);
        
        for (int i = 0; i < columns.length; ++i) {
            Object value = values[i];
            Column column = columns[i];
            Style style = styles[i];
            if (value == null) {
                if (explicitNulls[i]) {
                    insertBuilder = insertBuilder.value(column, value, style);
                }
            } else {
                insertBuilder = insertBuilder.value(column, value, style);
            }
        }
        
        return insertBuilder.toSql();
    }
}
